<?php
/**
 * CK框架 webClient类 用于请求网页
 * @author wujibing<283109896@qq.com>
 * @version   3.0
 * @category   CkFrameWork
 * @package  CkFrameWork
 * @subpackage lib
 */
class webClient
{
    /**
     * 请求报头
     * @var array
     */
    private static $_requestHeader = array(
            'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
            'Accept-Charset' => 'GB2312,utf-8;q=0.7,*;q=0.7',
            'Cache-Control' => 'no-cache',
            'Connection' =>  'Close',
            'Accept-Language' => 'zh-cn,zh;q=0.5',
            'Accept-Encoding' => 'gzip, deflate',
            );
    /**
     * 设置time out
     * @var int
     */
    private static $_timeOut = 30;
    /**
     * 响应报头
     * @var array
     */
    private static $_responseHeader = array();
    
    /**
     * 根据键获取返回的报头信息
     * 键名一定要小写
     * <code>
     * $baiduContent = webClient::get('http://baidu.com');
     * //获取百度设置的服务器信息
     * echo webClient::getResponseHeader('server');
     * </code>
     * @param string $key
     * @return string
     */
    public static function getResponseHeader($key)
    {
        return isset(self::$_responseHeader[$key]) ? self::$_responseHeader[$key] : '';    
    }
    /**
     * 添加请求报头信息
     * @param string $headerName 请求头名 可参阅fixfox发送的报头信息
     * @param string $headerValue 请求头值
     * <code>
     * //请求百度。并且添加一个cookie报头
     * $cookieValue = 'BAIDUID=4ABEB9071A1786DE2C5BF70567ACD98F';
     * webClient::addHeader('Cookie',$cookieValue);
     * echo webClient::get('http://www.baidu.com');
     * </code>
     */
    public static function addHeader($headerName,$headerValue)
    {
        self::$_requestHeader[$headerName] = $headerValue;
    }
    /**
     * 设置超时时间
     * @param int $timeOut
     * <code>
     * webClient::setTimeOut(40);
     * </code>
     */
    public static function setTimeOut($timeOut)
    {
        $timeOut = (int)$timeOut;
        $timeOut < 1 ? $timeOut = 30 : '';
        self::$_timeOut = $timeOut;
    }
    /**
     * get一个网页
     * <code>
     * echo webClient::get('http://baidu.com');
     * </code>
     * @param string $url 请求的网页
     * @return string
     */
    public static function get($url)
    {
        return self::request($url, 'GET');
    }
    /**
     * post提交数据到一个网页
     * $url参数为请求的网页地址
     * $data为请求的POST数组信息 键对应值
     * 向百度构建一个POST请求 POST q = ye; text = iiee
     * <code>
     * $postData = array('q'=>'ye','text' => 'iiee');
     * echo webClient::post('http://www.baidu.com/',$postData);
     * </code>
     * @param string $url 请求的网页
     * @param array $data post的数据
     */
    public static function post($url,$data)
    {
        $postString = '';
        if($data) foreach ($data as $key => $value)
        {
            $postString .= $key.'='.$value.'&';
        }
        return self::request($url, 'POST',$postString);
    } 
    /**
     * 上传文件 如果有POST信息则一起提交POST数组
     * 向百度构建POST上传2张表单名为file1和file2的图片。
     * 并且POST q = ye; text = iiee数据。
     * <code>
     * $fileArray = array(
     *     array('filePath'=>'F:/w.jpg','fileName'=>'w.jpg','formName'=>'file1','fileType'=>'image/jpeg'),
     *     array('filePath'=>'F:/w2.jpg','fileName'=>'w2.jpg','formName'=>'file2','fileType'=>'image/jpeg'),
     *     );
     * $postData = array('q'=>'ye','text' => 'iiee');
     * echo webClient::upload('http://baidu.com',$fileArray,$postData);
     * </code>
     * @param string $url 上传URL地址
     * @param array $uploadFiles 上传的文件数组
     * array[0] = array(filePath=>'文件的绝对路径地址',fileType=>'文件的MIME',fileName=>'文件名','formName'=>'表单名');
     * @param array $data 上传的POST信息
     * array(key => value)
     * @throws uploadFileNotExistsException 上传文件不存在
     * @return string
     */
    public static function upload($url,$uploadFiles = array(),$data=array())
    {
        $boundary = '---------------------------' . substr(md5(time()), -12 );
        $postString = '';
        self::$_requestHeader['Content-Type'] = 'multipart/form-data;boundary='.$boundary;
        if(is_array($data) && $data) foreach ($data as $key => $value)
        {
            $postString .= '--'.$boundary."\r\n";
            $postString .= 'Content-Disposition: form-data; name="'.$key.'"'."\r\n\r\n\r\n";
            $postString .= $value ."\r\n";
        }
        //多文件上传
        if($uploadFiles && is_array($uploadFiles)) foreach ($uploadFiles as $value) 
        {
            if(!file_exists($value['filePath']))
            {
                //上传文件不存在异常
                throw new uploadFileNotExistsException($value['filePath']);    
            }
            $postString .= '--'.$boundary."\r\n";
            $postString .= 'Content-Disposition: form-data; name="'.$value['formName'].'"; filename="'.$value['fileName'].'"'."\r\n";
            $postString .= 'Content-Type: '.$value['fileType']."\r\n\r\n";
            $postString .= file_get_contents($value['filePath']) ."\r\n";
        }
        $postString .= '--'.$boundary."--\r\n";
        return self::request($url, 'POST',$postString,true);
    }
    /**
     * 请求网页
     * @param string $url 请求地址
     * @param string $method get or post
     * @param string $postString post信息
     * @param bool $uploadFile 是否上传文件
     * @throws canNotConnectSocketException 不能连接服务器异常
     * @return string
     */
    private static function request($url,$method,$postString = '',$uploadFile = false)
    {
        $host = self::getHostByUrl($url);
        $path = $host['path'] ? $host['path'].($host['query'] ? '?'.$host['query'] : '') : '/';
        $port = !empty($host['port']) ? $host['port'] : 80;
        
        $requestHeader = $method.' '. $path.' HTTP/1.1'."\r\n";
        
        //判断有没设置refer
        if(!self::$_requestHeader['Referer'])
        {
            self::$_requestHeader['Referer'] = 'http://'.$host['host'];
        }
        if(!self::$_requestHeader['User-Agent'])
        {
            self::$_requestHeader['User-Agent'] = $_SERVER['HTTP_USER_AGENT'] ? $_SERVER['HTTP_USER_AGENT'] : 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:8.0) Gecko/20100101 Firefox/8.0';
        }
        self::$_requestHeader['Host'] = $host['host'];
        
        if($method == 'POST')
        {
            if(!$uploadFile)
            {
                self::$_requestHeader['Content-Type'] = 'application/x-www-form-urlencoded';
            }
            self::$_requestHeader['Content-Length'] = strlen($postString);
            $requestHeader .= self::getHeaderString();
            $requestHeader .= "\r\n".$postString;
        } else 
        {
            $requestHeader .= self::getHeaderString();
        }
        $requestHeader .= "\r\n\r\n";
        //构建HEADER信息完成
        $fp = @fsockopen($host['host'] ? $host['host'] : $host['ip'], $port, $errno, $errstr, self::$_timeOut);
        if(!$fp)
        {
            throw new canNotConnectSocketException($url,$errstr);
        }
        stream_set_blocking($fp, true);
        stream_set_timeout($fp, self::$_timeOut);
        @fwrite($fp,$requestHeader);
        $status = stream_get_meta_data($fp);
        $tmpArr = self::$_responseHeader = array();
        $response = '';
        if(!$status['timed_out'])
        {
            while (!feof($fp))
            {
                if(($header = @fgets($fp)) && ($header == "\r\n" ||  $header == "\n"))
                {
                    break;
                }
                $tmpArr = explode(': ',$header);
                $tmpArr[0] = trim(strtolower($tmpArr[0]));
                $tmpArr[1] = trim($tmpArr[1]);
                isset(self::$_responseHeader[$tmpArr[0]]) ? self::$_responseHeader[$tmpArr[0]] .= "\r\n".$tmpArr[1]:self::$_responseHeader[$tmpArr[0]] = $tmpArr[1];
            }
            while(!feof($fp))
            {
                $data = fread($fp, 10240);
                $response .= $data;
            }
        }
        @fclose($fp);
        if(self::$_responseHeader['transfer-encoding'] == 'chunked')
        {
            self::unChunked($response);
        }
        if(self::$_responseHeader['content-encoding'] == 'gzip')
        {
            self::unGzip($response);
        }
        return $response;
    }
    /**
     * chunked解压
     * @param string $return
     */
    private static function unChunked(&$string)
    {
        $fp = 0;
        $outData = '';
        while ($fp < strlen($string)) 
        {
            $rawnum = substr($string, $fp, strpos(substr($string, $fp), "\r\n") + 2);
            $num = hexdec(trim($rawnum));
            $fp += strlen($rawnum);
            $chunk = substr($string, $fp, $num);
            $outData .= $chunk;
            $fp += strlen($chunk);
        }
        $string = $outData;
    }
    /**
     * 根据HEADER数组生成HEADER
     */
    private static function getHeaderString()
    {
        $string = '';
        if(self::$_requestHeader) foreach (self::$_requestHeader as $key => $value)
        {
            $string .=  $key.':'.$value."\r\n";
        }
        return $string;
    }
    /**
     * 返回URL HOST等信息
     * @param string $url
     */
    public static function getHostByUrl($url)
    {
        $host = parse_url($url);
        $ip = gethostbyname($host['host']);
        $ipLong = ip2long($ip);
        if($ipLong == -1 || $ipLong === false)
        {
            $host['ip'] = '127.0.0.1';
        }
        $host['ip'] = $ip;
        return $host;
    }
    /**
     * gzip解压
     * @param string $data
     */
    public static function unGzip(&$data)
    {
        $len = strlen($data);
        if ($len < 18 || strcmp( substr($data,0,2),"\x1f\x8b"))
        {
            return null;  // Not GZIP format (See RFC 1952)
        }
        $method = ord(substr($data,2,1));  // Compression method
        $flags  = ord(substr($data,3,1));  // Flags
        if ($flags & 31 != $flags)
        {
            return null;
        }
        // NOTE: $mtime may be negative (PHP integer limitations)
        $mtime = unpack("V", substr($data,4,4));
        $mtime = $mtime[1];
        $xfl   = substr($data,8,1);
        $os    = substr($data,8,1);
        $headerlen = 10;
        $extralen  = 0;
        $extra     = "";
        if ($flags & 4) {
            // 2-byte length prefixed EXTRA data in header
            if ($len - $headerlen - 2 < 8) {
                return false;    // Invalid format
            }
            $extralen = unpack("v",substr($data,8,2));
            $extralen = $extralen[1];
            if ($len - $headerlen - 2 - $extralen < 8) {
                return false;    // Invalid format
            }
            $extra = substr($data,10,$extralen);
            $headerlen += 2 + $extralen;
        }
    
        $filenamelen = 0;
        $filename = "";
        if ($flags & 8) {
            // C-style string file NAME data in header
            if ($len - $headerlen - 1 < 8) {
                return false;    // Invalid format
            }
            $filenamelen = strpos(substr($data,8+$extralen),chr(0));
            if ($filenamelen === false || $len - $headerlen - $filenamelen - 1 < 8) {
                return false;    // Invalid format
            }
            $filename = substr($data,$headerlen,$filenamelen);
            $headerlen += $filenamelen + 1;
        }
    
        $commentlen = 0;
        $comment = "";
        if ($flags & 16) {
            // C-style string COMMENT data in header
            if ($len - $headerlen - 1 < 8) {
                return false;    // Invalid format
            }
            $commentlen = strpos(substr($data,8+$extralen+$filenamelen),chr(0));
            if ($commentlen === false || $len - $headerlen - $commentlen - 1 < 8) {
                return false;    // Invalid header format
            }
            $comment = substr($data,$headerlen,$commentlen);
            $headerlen += $commentlen + 1;
        }
    
        $headercrc = "";
        if ($flags & 2) {
            // 2-bytes (lowest order) of CRC32 on header present
            if ($len - $headerlen - 2 < 8) {
                return false;    // Invalid format
            }
            $calccrc = crc32(substr($data,0,$headerlen)) & 0xffff;
            $headercrc = unpack("v", substr($data,$headerlen,2));
            $headercrc = $headercrc[1];
            if ($headercrc != $calccrc) {
                return false;    // Bad header CRC
            }
            $headerlen += 2;
        }
    
        // GZIP FOOTER - These be negative due to PHP's limitations
        $datacrc = unpack("V",substr($data,-8,4));
        $datacrc = $datacrc[1];
        $isize = unpack("V",substr($data,-4));
        $isize = $isize[1];
    
        // Perform the decompression:
        $bodylen = $len-$headerlen-8;
        if ($bodylen < 1) {
            // This should never happen - IMPLEMENTATION BUG!
            return null;
        }
        $body = substr($data,$headerlen,$bodylen);
        $newData = '';
        if ($bodylen > 0) {
            switch ($method) {
                case 8:
                    // Currently the only supported compression method:
                    $newData = gzinflate($body);
                    break;
                default:
                    // Unknown compression method
                    return false;
            }
        }
    
        // Verifiy decompressed size and CRC32:
        // NOTE: This may fail with large data sizes depending on how
        //       PHP's integer limitations affect strlen() since $isize
        //       may be negative for large sizes.
        if ($isize != strlen($newData) || crc32($newData) != $datacrc) {
            // Bad format!  Length or CRC doesn't match!
            return false;
        }
        $data = $newData;
        return true;
    }
}


/**
 * CK框架 通过webClient上传文件不存在异常
 * @author wujibing<283109896@qq.com>
 * @version   3.0
 * @category   CkFrameWork
 * @package  CkFrameWork
 * @subpackage lib
 */
class uploadFileNotExistsException extends Exception
{
    public function __construct($fileName)
    {
        parent::__construct(lang::getLang('__WEB_CLIENT_FILE_NOT_EXISTS',array($fileName)));
    }
}


/**
 * CK框架 webClient不能连接异常
 * @author wujibing<283109896@qq.com>
 * @version   3.0
 * @category   CkFrameWork
 * @package  CkFrameWork
 * @subpackage lib
 */
class canNotConnectSocketException extends Exception
{
    public function __construct($url,$errString)
    {
        parent::__construct(lang::getLang('__WEB_CLIENT_SOCKET_ERROR__',array($url,$errString)));
    }
}